// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#pragma once

#include <memory>
#include <sstream>

#include <fmt/format.h>
#include <fmt/ostream.h>
#include <melon/preprocessor.h>

#include <pollux/common/base/fmt_std_formatters.h>
#include <pollux/common/base/pollux_exception.h>

namespace kumo::pollux {
    namespace detail {
        struct PolluxCheckFailArgs {
            const char *file;
            size_t line;
            const char *function;
            const char *expression;
            const char *errorSource;
            const char *errorCode;
            bool isRetriable;
        };

        struct CompileTimeEmptyString {
            CompileTimeEmptyString() = default;

            constexpr operator const char *() const {
                return "";
            }

            constexpr operator std::string_view() const {
                return {};
            }

            operator std::string() const {
                return {};
            }
        };

        // polluxCheckFail is defined as a separate helper function rather than
        // a macro or inline `throw` expression to allow the compiler *not* to
        // inline it when it is large. Having an out-of-line error path helps
        // otherwise-small functions that call error-checking macros stay
        // small and thus stay eligible for inlining.
        template<typename Exception, typename StringType>
        [[noreturn]] void polluxCheckFail(const PolluxCheckFailArgs &args, StringType s) {
            static_assert(
                !std::is_same_v<StringType, std::string>,
                "BUG: we should not pass std::string by value to polluxCheckFail");
            if constexpr (!std::is_same_v<Exception, PolluxUserError>) {
                KLOG(ERROR) << "Line: " << args.file << ":" << args.line
                        << ", Function:" << args.function
                        << ", Expression: " << args.expression << " " << s
                        << ", Source: " << args.errorSource
                        << ", ErrorCode: " << args.errorCode;
            }

            ++threadNumPolluxThrow();
            throw Exception(
                args.file,
                args.line,
                args.function,
                args.expression,
                s,
                args.errorSource,
                args.errorCode,
                args.isRetriable);
        }

        // PolluxCheckFailStringType helps us pass by reference to
        // polluxCheckFail exactly when the string type is std::string.
        template<typename T>
        struct PolluxCheckFailStringType;

        template<>
        struct PolluxCheckFailStringType<CompileTimeEmptyString> {
            using type = CompileTimeEmptyString;
        };

        template<>
        struct PolluxCheckFailStringType<const char *> {
            using type = const char *;
        };

        template<>
        struct PolluxCheckFailStringType<std::string> {
            using type = const std::string &;
        };

        // Declare explicit instantiations of polluxCheckFail for the given
        // exceptionType. Just like normal function declarations (prototypes),
        // this allows the compiler to assume that they are defined elsewhere
        // and simply insert a function call for the linker to fix up, rather
        // than emitting a definition of these templates into every
        // translation unit they are used in.
#define DECLARE_CHECK_FAIL_TEMPLATES(exception_type)                           \
  namespace detail {                                                           \
  extern template void polluxCheckFail<exception_type, CompileTimeEmptyString>( \
      const PolluxCheckFailArgs& args,                                          \
      CompileTimeEmptyString);                                                 \
  extern template void polluxCheckFail<exception_type, const char*>(            \
      const PolluxCheckFailArgs& args,                                          \
      const char*);                                                            \
  extern template void polluxCheckFail<exception_type, const std::string&>(     \
      const PolluxCheckFailArgs& args,                                          \
      const std::string&);                                                     \
  } // namespace detail

        // Definitions corresponding to DECLARE_CHECK_FAIL_TEMPLATES. Should
        // only be used in Exceptions.cpp.
#define DEFINE_CHECK_FAIL_TEMPLATES(exception_type)                     \
  template void polluxCheckFail<exception_type, CompileTimeEmptyString>( \
      const PolluxCheckFailArgs& args, CompileTimeEmptyString);          \
  template void polluxCheckFail<exception_type, const char*>(            \
      const PolluxCheckFailArgs& args, const char*);                     \
  template void polluxCheckFail<exception_type, const std::string&>(     \
      const PolluxCheckFailArgs& args, const std::string&);

        // When there is no message passed, we can statically detect this case
        // and avoid passing even a single unnecessary argument pointer,
        // minimizing size and thus maximizing eligibility for inlining.
        inline CompileTimeEmptyString errorMessage() {
            return {};
        }

        inline const char *errorMessage(const char *s) {
            return s;
        }

        inline std::string errorMessage(const std::string &str) {
            return str;
        }

        template<typename... Args>
        std::string errorMessage(fmt::string_view fmt, const Args &... args) {
            return fmt::vformat(fmt, fmt::make_format_args(args...));
        }
    } // namespace detail

#define _POLLUX_THROW_IMPL(                                               \
    exception, exprStr, errorSource, errorCode, isRetriable, ...)        \
  do {                                                                   \
    /* GCC 9.2.1 doesn't accept this code with constexpr. */             \
    static const ::kumo::pollux::detail::PolluxCheckFailArgs           \
        polluxCheckFailArgs = {                                           \
            __FILE__,                                                    \
            __LINE__,                                                    \
            __FUNCTION__,                                                \
            exprStr,                                                     \
            errorSource,                                                 \
            errorCode,                                                   \
            isRetriable};                                                \
    auto message = ::kumo::pollux::detail::errorMessage(__VA_ARGS__); \
    ::kumo::pollux::detail::polluxCheckFail<                           \
        exception,                                                       \
        typename ::kumo::pollux::detail::PolluxCheckFailStringType<    \
            decltype(message)>::type>(polluxCheckFailArgs, message);      \
  } while (0)

#define _POLLUX_CHECK_AND_THROW_IMPL(                                    \
    expr, exprStr, exception, errorSource, errorCode, isRetriable, ...) \
  do {                                                                  \
    if (UNLIKELY(!(expr))) {                                            \
      _POLLUX_THROW_IMPL(                                                \
          exception,                                                    \
          exprStr,                                                      \
          errorSource,                                                  \
          errorCode,                                                    \
          isRetriable,                                                  \
          __VA_ARGS__);                                                 \
    }                                                                   \
  } while (0)

#define _POLLUX_THROW(exception, ...) \
  _POLLUX_THROW_IMPL(exception, "", ##__VA_ARGS__)

    DECLARE_CHECK_FAIL_TEMPLATES(::kumo::pollux::PolluxRuntimeError)

#define _POLLUX_CHECK_IMPL(expr, exprStr, ...)                       \
  _POLLUX_CHECK_AND_THROW_IMPL(                                      \
      expr,                                                         \
      exprStr,                                                      \
      ::kumo::pollux::PolluxRuntimeError,                         \
      ::kumo::pollux::error_source::kErrorSourceRuntime.c_str(), \
      ::kumo::pollux::error_code::kInvalidState.c_str(),         \
      /* isRetriable */ false,                                      \
      ##__VA_ARGS__)

    /// Throws PolluxRuntimeError when functions receive input values out of the
    /// supported range. This should only be used when we want to force TRY() to not
    /// suppress the error.
#define POLLUX_CHECK_UNSUPPORTED_INPUT_UNCATCHABLE(expr, ...)                   \
  do {                                                                         \
    if (UNLIKELY(!(expr))) {                                                   \
      _POLLUX_THROW_IMPL(                                                       \
          ::kumo::pollux::PolluxRuntimeError,                                \
          #expr,                                                               \
          ::kumo::pollux::error_source::kErrorSourceRuntime.c_str(),        \
          ::kumo::pollux::error_code::kUnsupportedInputUncatchable.c_str(), \
          /* isRetriable */ false,                                             \
          __VA_ARGS__);                                                        \
    }                                                                          \
  } while (0)

    // If the caller passes a custom message (4 *or more* arguments), we
    // have to construct a format string from ours ("({} vs. {})") plus
    // theirs by adding a space and shuffling arguments. If they don't (exactly 3
    // arguments), we can just pass our own format string and arguments straight
    // through.

#define _POLLUX_CHECK_OP_WITH_USER_FMT_HELPER(   \
    implmacro, expr1, expr2, op, user_fmt, ...) \
  implmacro(                                    \
      (expr1)op(expr2),                         \
      #expr1 " " #op " " #expr2,                \
      "({} vs. {}) " user_fmt,                  \
      expr1,                                    \
      expr2,                                    \
      ##__VA_ARGS__)

#define _POLLUX_CHECK_OP_HELPER(implmacro, expr1, expr2, op, ...) \
  do {                                                           \
    if constexpr (MELON_PP_DETAIL_NARGS(__VA_ARGS__) > 0) {      \
      _POLLUX_CHECK_OP_WITH_USER_FMT_HELPER(                      \
          implmacro, expr1, expr2, op, __VA_ARGS__);             \
    } else {                                                     \
      implmacro(                                                 \
          (expr1)op(expr2),                                      \
          #expr1 " " #op " " #expr2,                             \
          "({} vs. {})",                                         \
          expr1,                                                 \
          expr2);                                                \
    }                                                            \
  } while (0)

#define _POLLUX_CHECK_OP(expr1, expr2, op, ...) \
  _POLLUX_CHECK_OP_HELPER(_POLLUX_CHECK_IMPL, expr1, expr2, op, ##__VA_ARGS__)

#define _POLLUX_USER_CHECK_IMPL(expr, exprStr, ...)               \
  _POLLUX_CHECK_AND_THROW_IMPL(                                   \
      expr,                                                      \
      exprStr,                                                   \
      ::kumo::pollux::PolluxUserError,                         \
      ::kumo::pollux::error_source::kErrorSourceUser.c_str(), \
      ::kumo::pollux::error_code::kInvalidArgument.c_str(),   \
      /* isRetriable */ false,                                   \
      ##__VA_ARGS__)

#define _POLLUX_USER_CHECK_OP(expr1, expr2, op, ...) \
  _POLLUX_CHECK_OP_HELPER(                           \
      _POLLUX_USER_CHECK_IMPL, expr1, expr2, op, ##__VA_ARGS__)

    // For all below macros, an additional message can be passed using a
    // format string and arguments, as with `fmt::format`.
#define POLLUX_CHECK(expr, ...) _POLLUX_CHECK_IMPL(expr, #expr, ##__VA_ARGS__)
#define POLLUX_CHECK_GT(e1, e2, ...) _POLLUX_CHECK_OP(e1, e2, >, ##__VA_ARGS__)
#define POLLUX_CHECK_GE(e1, e2, ...) _POLLUX_CHECK_OP(e1, e2, >=, ##__VA_ARGS__)
#define POLLUX_CHECK_LT(e1, e2, ...) _POLLUX_CHECK_OP(e1, e2, <, ##__VA_ARGS__)
#define POLLUX_CHECK_LE(e1, e2, ...) _POLLUX_CHECK_OP(e1, e2, <=, ##__VA_ARGS__)
#define POLLUX_CHECK_EQ(e1, e2, ...) _POLLUX_CHECK_OP(e1, e2, ==, ##__VA_ARGS__)
#define POLLUX_CHECK_NE(e1, e2, ...) _POLLUX_CHECK_OP(e1, e2, !=, ##__VA_ARGS__)
#define POLLUX_CHECK_NULL(e, ...) POLLUX_CHECK(e == nullptr, ##__VA_ARGS__)
#define POLLUX_CHECK_NOT_NULL(e, ...) POLLUX_CHECK(e != nullptr, ##__VA_ARGS__)

#define POLLUX_CHECK_OK(expr)                          \
  do {                                                \
    ::kumo::pollux::Status _s = (expr);            \
    _POLLUX_CHECK_IMPL(_s.ok(), #expr, _s.toString()); \
  } while (false)

#define POLLUX_UNSUPPORTED(...)                                   \
  _POLLUX_THROW(                                                  \
      ::kumo::pollux::PolluxUserError,                         \
      ::kumo::pollux::error_source::kErrorSourceUser.c_str(), \
      ::kumo::pollux::error_code::kUnsupported.c_str(),       \
      /* isRetriable */ false,                                   \
      ##__VA_ARGS__)

#define POLLUX_ARITHMETIC_ERROR(...)                              \
  _POLLUX_THROW(                                                  \
      ::kumo::pollux::PolluxUserError,                         \
      ::kumo::pollux::error_source::kErrorSourceUser.c_str(), \
      ::kumo::pollux::error_code::kArithmeticError.c_str(),   \
      /* isRetriable */ false,                                   \
      ##__VA_ARGS__)

#define POLLUX_SCHEMA_MISMATCH_ERROR(...)                         \
  _POLLUX_THROW(                                                  \
      ::kumo::pollux::PolluxUserError,                         \
      ::kumo::pollux::error_source::kErrorSourceUser.c_str(), \
      ::kumo::pollux::error_code::kSchemaMismatch.c_str(),    \
      /* isRetriable */ false,                                   \
      ##__VA_ARGS__)

#define POLLUX_FILE_NOT_FOUND_ERROR(...)                             \
  _POLLUX_THROW(                                                     \
      ::kumo::pollux::PolluxRuntimeError,                         \
      ::kumo::pollux::error_source::kErrorSourceRuntime.c_str(), \
      ::kumo::pollux::error_code::kFileNotFound.c_str(),         \
      /* isRetriable */ false,                                      \
      ##__VA_ARGS__)

#define POLLUX_UNREACHABLE(...)                                      \
  _POLLUX_THROW(                                                     \
      ::kumo::pollux::PolluxRuntimeError,                         \
      ::kumo::pollux::error_source::kErrorSourceRuntime.c_str(), \
      ::kumo::pollux::error_code::kUnreachableCode.c_str(),      \
      /* isRetriable */ false,                                      \
      ##__VA_ARGS__)

#ifndef NDEBUG
#define POLLUX_DCHECK(expr, ...) POLLUX_CHECK(expr, ##__VA_ARGS__)
#define POLLUX_DCHECK_GT(e1, e2, ...) POLLUX_CHECK_GT(e1, e2, ##__VA_ARGS__)
#define POLLUX_DCHECK_GE(e1, e2, ...) POLLUX_CHECK_GE(e1, e2, ##__VA_ARGS__)
#define POLLUX_DCHECK_LT(e1, e2, ...) POLLUX_CHECK_LT(e1, e2, ##__VA_ARGS__)
#define POLLUX_DCHECK_LE(e1, e2, ...) POLLUX_CHECK_LE(e1, e2, ##__VA_ARGS__)
#define POLLUX_DCHECK_EQ(e1, e2, ...) POLLUX_CHECK_EQ(e1, e2, ##__VA_ARGS__)
#define POLLUX_DCHECK_NE(e1, e2, ...) POLLUX_CHECK_NE(e1, e2, ##__VA_ARGS__)
#define POLLUX_DCHECK_NULL(e, ...) POLLUX_CHECK_NULL(e, ##__VA_ARGS__)
#define POLLUX_DCHECK_NOT_NULL(e, ...) POLLUX_CHECK_NOT_NULL(e, ##__VA_ARGS__)
#define POLLUX_DEBUG_ONLY
#else
#define POLLUX_DCHECK(expr, ...) POLLUX_CHECK(true)
#define POLLUX_DCHECK_GT(e1, e2, ...) POLLUX_CHECK(true)
#define POLLUX_DCHECK_GE(e1, e2, ...) POLLUX_CHECK(true)
#define POLLUX_DCHECK_LT(e1, e2, ...) POLLUX_CHECK(true)
#define POLLUX_DCHECK_LE(e1, e2, ...) POLLUX_CHECK(true)
#define POLLUX_DCHECK_EQ(e1, e2, ...) POLLUX_CHECK(true)
#define POLLUX_DCHECK_NE(e1, e2, ...) POLLUX_CHECK(true)
#define POLLUX_DCHECK_NULL(e, ...) POLLUX_CHECK(true)
#define POLLUX_DCHECK_NOT_NULL(e, ...) POLLUX_CHECK(true)
#define POLLUX_DEBUG_ONLY [[maybe_unused]]
#endif

#define POLLUX_FAIL(...)                                             \
  _POLLUX_THROW(                                                     \
      ::kumo::pollux::PolluxRuntimeError,                         \
      ::kumo::pollux::error_source::kErrorSourceRuntime.c_str(), \
      ::kumo::pollux::error_code::kInvalidState.c_str(),         \
      /* isRetriable */ false,                                      \
      ##__VA_ARGS__)

    /// Throws PolluxRuntimeError when functions receive input values out of the
    /// supported range. This should only be used when we want to force TRY() to not
    /// suppress the error.
#define POLLUX_FAIL_UNSUPPORTED_INPUT_UNCATCHABLE(...)                      \
  _POLLUX_THROW(                                                            \
      ::kumo::pollux::PolluxRuntimeError,                                \
      ::kumo::pollux::error_source::kErrorSourceRuntime.c_str(),        \
      ::kumo::pollux::error_code::kUnsupportedInputUncatchable.c_str(), \
      /* isRetriable */ false,                                             \
      ##__VA_ARGS__)

    DECLARE_CHECK_FAIL_TEMPLATES(::kumo::pollux::PolluxUserError)

    // For all below macros, an additional message can be passed using a
    // format string and arguments, as with `fmt::format`.
#define POLLUX_USER_CHECK(expr, ...) \
  _POLLUX_USER_CHECK_IMPL(expr, #expr, ##__VA_ARGS__)
#define POLLUX_USER_CHECK_GT(e1, e2, ...) \
  _POLLUX_USER_CHECK_OP(e1, e2, >, ##__VA_ARGS__)
#define POLLUX_USER_CHECK_GE(e1, e2, ...) \
  _POLLUX_USER_CHECK_OP(e1, e2, >=, ##__VA_ARGS__)
#define POLLUX_USER_CHECK_LT(e1, e2, ...) \
  _POLLUX_USER_CHECK_OP(e1, e2, <, ##__VA_ARGS__)
#define POLLUX_USER_CHECK_LE(e1, e2, ...) \
  _POLLUX_USER_CHECK_OP(e1, e2, <=, ##__VA_ARGS__)
#define POLLUX_USER_CHECK_EQ(e1, e2, ...) \
  _POLLUX_USER_CHECK_OP(e1, e2, ==, ##__VA_ARGS__)
#define POLLUX_USER_CHECK_NE(e1, e2, ...) \
  _POLLUX_USER_CHECK_OP(e1, e2, !=, ##__VA_ARGS__)
#define POLLUX_USER_CHECK_NULL(e, ...) \
  POLLUX_USER_CHECK(e == nullptr, ##__VA_ARGS__)
#define POLLUX_USER_CHECK_NOT_NULL(e, ...) \
  POLLUX_USER_CHECK(e != nullptr, ##__VA_ARGS__)

#ifndef NDEBUG
#define POLLUX_USER_DCHECK(expr, ...) POLLUX_USER_CHECK(expr, ##__VA_ARGS__)
#define POLLUX_USER_DCHECK_GT(e1, e2, ...) \
  POLLUX_USER_CHECK_GT(e1, e2, ##__VA_ARGS__)
#define POLLUX_USER_DCHECK_GE(e1, e2, ...) \
  POLLUX_USER_CHECK_GE(e1, e2, ##__VA_ARGS__)
#define POLLUX_USER_DCHECK_LT(e1, e2, ...) \
  POLLUX_USER_CHECK_LT(e1, e2, ##__VA_ARGS__)
#define POLLUX_USER_DCHECK_LE(e1, e2, ...) \
  POLLUX_USER_CHECK_LE(e1, e2, ##__VA_ARGS__)
#define POLLUX_USER_DCHECK_EQ(e1, e2, ...) \
  POLLUX_USER_CHECK_EQ(e1, e2, ##__VA_ARGS__)
#define POLLUX_USER_DCHECK_NE(e1, e2, ...) \
  POLLUX_USER_CHECK_NE(e1, e2, ##__VA_ARGS__)
#define POLLUX_USER_DCHECK_NOT_NULL(e, ...) \
  POLLUX_USER_CHECK_NOT_NULL(e, ##__VA_ARGS__)
#define POLLUX_USER_DCHECK_NULL(e, ...) POLLUX_USER_CHECK_NULL(e, ##__VA_ARGS__)
#else
#define POLLUX_USER_DCHECK(expr, ...) POLLUX_USER_CHECK(true)
#define POLLUX_USER_DCHECK_GT(e1, e2, ...) POLLUX_USER_CHECK(true)
#define POLLUX_USER_DCHECK_GE(e1, e2, ...) POLLUX_USER_CHECK(true)
#define POLLUX_USER_DCHECK_LT(e1, e2, ...) POLLUX_USER_CHECK(true)
#define POLLUX_USER_DCHECK_LE(e1, e2, ...) POLLUX_USER_CHECK(true)
#define POLLUX_USER_DCHECK_EQ(e1, e2, ...) POLLUX_USER_CHECK(true)
#define POLLUX_USER_DCHECK_NE(e1, e2, ...) POLLUX_USER_CHECK(true)
#define POLLUX_USER_DCHECK_NULL(e, ...) POLLUX_USER_CHECK(true)
#define POLLUX_USER_DCHECK_NOT_NULL(e, ...) POLLUX_USER_CHECK(true)
#endif

#define POLLUX_USER_FAIL(...)                                     \
  _POLLUX_THROW(                                                  \
      ::kumo::pollux::PolluxUserError,                         \
      ::kumo::pollux::error_source::kErrorSourceUser.c_str(), \
      ::kumo::pollux::error_code::kInvalidArgument.c_str(),   \
      /* isRetriable */ false,                                   \
      ##__VA_ARGS__)

#define POLLUX_NYI(...)                                              \
  _POLLUX_THROW(                                                     \
      ::kumo::pollux::PolluxRuntimeError,                         \
      ::kumo::pollux::error_source::kErrorSourceRuntime.c_str(), \
      ::kumo::pollux::error_code::kNotImplemented.c_str(),       \
      /* isRetriable */ false,                                      \
      ##__VA_ARGS__)
} // namespace kumo::pollux
